組件允許我們將 UI 劃分為獨立的、可重用的部分,並且可以對每個部分進行單獨的思考。在實際應用中,組件常常被組織成層層嵌套的樹狀結構:
這和我們嵌套 HTML 元素的方式相似,Vue 實現了自己的組件模型,使我們可以在每個組件內封裝自定義內容與邏輯。Vue 同樣也能很好地配合原生 Web Component。
當使用構建步驟時,我們一般會將 Vue 組件定義在一個單獨的 .vue 文件中,這被叫做單文件組件 (簡稱 SFC):
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
當不使用構建步驟時,一個 Vue 組件以一個包含 Vue 特定選項的 JavaScript 對象來定義:
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
// 也可以針對一個 DOM 內聯模板:
// template: '#my-template-element'
}
這裡的模板是一個內聯的 JavaScript 字符串,Vue 將會在運行時編譯它。你也可以使用 ID 選擇器來指向一個元素 (通常是原生的 template 元素),Vue 將會使用其內容作為模板來源。
上面的例子中定義了一個組件,並在一個 .js 文件裡默認導出了它自己,但你也可以通過具名導出在一個文件中導出多個組件。
我們會在接下來的指引中使用 SFC 語法,無論你是否使用構建步驟,組件相關的概念都是相同的。示例一節中展示了兩種場景中的組件使用情況。
要使用一個子組件,我們需要在父組件中導入它。假設我們把計數器組件放在了一個叫做 ButtonCounter.vue 的文件中,這個組件將會以默認導出的形式被暴露給外部。
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
通過 script setup,導入的組件都在模板中直接可用。
當然,你也可以全局地註冊一個組件,使得它在當前應用中的任何組件上都可以使用,而不需要額外再導入。關於組件的全局註冊和局部註冊兩種方式的利弊,我們放在了組件註冊這一章節中專門討論。
組件可以被重用任意多次:
<h1>Here is a child component!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />
你會注意到,每當點擊這些按鈕時,每一個組件都維護著自己的狀態,是不同的 count。這是因為每當你使用一個組件,就創建了一個新的實例。
在單文件組件中,推薦為子組件使用 PascalCase 的標籤名,以此來和原生的 HTML 元素作區分。雖然原生 HTML 標籤名是不區分大小寫的,但 Vue 單文件組件是可以在編譯中區分大小寫的。我們也可以使用 /> 來關閉一個標籤。
如果你是直接在 DOM 中書寫模板 (例如原生 template 元素的內容),模板的編譯需要遵從瀏覽器中 HTML 的解析行為。在這種情況下,你應該需要使用 kebab-case 形式並顯式地關閉這些組件的標籤。
<!-- 如果是在 DOM 中書寫該模板 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
如果我們正在構建一個博客,我們可能需要一個表示博客文章的組件。我們希望所有的博客文章分享相同的視覺佈局,但有不同的內容。要實現這樣的效果自然必須向組件中傳遞數據,例如每篇文章標題和內容,這就會使用到 props。
Props 是一種特別的 attributes,你可以在組件上聲明註冊。要傳遞給博客文章組件一個標題,我們必須在組件的 props 列表上聲明它。這裡要用到 defineProps 宏:
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>
defineProps 是一個僅 script setup 中可用的編譯宏命令,並不需要顯式地導入。聲明的 props 會自動暴露給模板。defineProps 會返回一個對象,其中包含了可以傳遞給組件的所有 props:
const props = defineProps(['title'])
console.log(props.title)
如果你沒有使用 script setup,props 必須以 props 選項的方式聲明,props 對象會作為 setup() 函數的第一個參數被傳入:
export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}
一個組件可以有任意數量的 props,默認情況下,所有 prop 都接受任意類型的值。
當一個 prop 被註冊後,可以像這樣以自定義 attribute 的形式傳遞數據給它:
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />
在實際應用中,我們可能在父組件中會有如下的一個博客文章數組:
const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
])
這種情況下,我們可以使用 v-for 來渲染它們:
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
留意我們是如何使用 v-bind 語法 (:title="post.title") 來傳遞動態 prop 值的。當事先不知道要渲染的確切內容時,這一點特別有用。